﻿namespace EFS.RC.CC.Presences.Operators.OfficeService
{
	internal class Agent : EFS.RC.CC.Presences.Operators.Deployment<EFS.RC.CC.Deployment.Entities.Operators.Agent>
	{
		#region Comonents
		
		// Sync access
		private object syncAccess = new object();

		// hunting queue
		private EFS.RC.CC.Tools.Hunters.Queue agentsHunterQueue = null;

		// Session
		private EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = null;

		// Skills
		private System.Collections.Generic.Dictionary<System.Int32, EFS.RC.CC.Deployment.Entities.Operators.Skills.Skill> skillsDictionary =
			new System.Collections.Generic.Dictionary<System.Int32, EFS.RC.CC.Deployment.Entities.Operators.Skills.Skill>();

		// Distribution list
		private System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent> agentDistributionList
			= new System.Collections.Generic.List<EFS.RC.CC.Presences.Operators.Distribution.Agent>();

		#endregion

		#region Delegates

		/// <summary>
		/// Deployment changes watch
		/// </summary>
		public delegate void DeploymentLoad(EFS.RC.CC.Presences.Operators.OfficeService.Agent agentOperator);

		/// <summary>
		/// Deployment watch delegate registrar
		/// </summary>
		public DeploymentLoad OnDeploymentLoad;

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="agent"></param>
		public Agent(EFS.RC.CC.Deployment.Entities.Operators.Agent agentDeployment, EFS.RC.CC.Applications.EndPoints.OfficeService.Operators operatorsOfficeServiceEndPointApplication, EFS.RC.CC.Tools.Hunters.Queue agentsHunterQueue)
			: base(agentDeployment, operatorsOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(agentsHunterQueue, "agentsHunterQueue");

			// keep the list
			this.agentsHunterQueue = agentsHunterQueue;

			// on status change
			OnPresenceAvailabilityChanged = AgentAvailabilityChanged;

			// load deployment
			LoadDeployment();
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format("EFS.RC.CC.Presences.Operators.OfficeService.Agent [{0}]", base.Describe());
		}

		/// Check if in session
		public System.Boolean InSession
		{
			get
			{
				lock (syncAccess)
				{
					// check ession not terminated
					return null != operatorSession && operatorSession.Status != Sessions.OfficeService.Operators.Operator.SessionStatus.Terminated;
				}
			}
		}

		/// <summary>
		/// Application
		/// </summary>
		public EFS.RC.CC.Sessions.OfficeService.Operators.Operator OperatorSession
		{
			get
			{
				return this.operatorSession;
			}
		}

		/// <summary>
		/// Register operator session
		/// </summary>
		/// <param name="operatorSession"></param>
		public void RegisterOperatorSession(EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession)
		{
			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// lock
			lock (syncAccess)
			{
				// check
				if (null != this.operatorSession && this.operatorSession.Status != Sessions.OfficeService.Operators.Operator.SessionStatus.Terminated)
				{
					throw new System.InvalidOperationException("Can not register new Operator Session while current Operator Session not Terminated");
				}

				try
				{
					// register
					this.operatorSession = operatorSession;
					this.operatorSession.AsinkTerminatedDelegate.Register(OnOperatorSessionTerminated);
				}
				catch (System.Exception ex)
				{
					// log
					EFS.EventLog.Log.LogException(ex, this);

					// remove
					this.operatorSession = null;
				}
			}
		}
		/// <summary>
		/// Register presence for distribution
		/// </summary>
		/// <param name="agentsDistribution"></param>
		/// <returns></returns>
		public EFS.RC.CC.Presences.Operators.Distribution.Agent RegisterForDistribution(EFS.RC.CC.Presences.Operators.Distribution.List agentsDistribution) 
		{
			// check
			EFS.Common.Verify.Reference(agentsDistribution, "agentsDistribution");

			// make new
			EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution = new EFS.RC.CC.Presences.Operators.Distribution.Agent(agentsDistribution, this);

			// register for ready
			agentDistribution.OnDistributeReady += AddedToDistribution;
			agentDistribution.OnDistributeStopped += RemovedFromDistribution;
		
			// done
			return agentDistribution;
		}

		/// <summary>
		/// Get distribution registration
		/// </summary>
		/// <param name="agentsDistribution"></param>
		/// <returns></returns>
		public EFS.RC.CC.Presences.Operators.Distribution.Agent GetDistributionRegistration(EFS.RC.CC.Presences.Operators.Distribution.List agentsDistribution)
		{
			// check
			EFS.Common.Verify.Reference(agentsDistribution, "agentsDistribution");

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				foreach(EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution in agentDistributionList)
				{
					// check 
					if (agentDistribution.AgentsDistribution.Equals(agentsDistribution))
					{
						return agentDistribution;
					}
				}
			}

			// not found
			return null;
		}

		/// <summary>
		/// Get status message for agent presence
		/// </summary>
		/// <returns></returns>
		public EFS.RC.CC.Messages.Agents.Status GetStatusMessage()
		{
			// new 
			EFS.RC.CC.Messages.Agents.Status status = new EFS.RC.CC.Messages.Agents.Status();

			// lock for changes
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// get info
				status.IsAvailable = IsAvailable;
				status.IsOnline = IsOnline;
				status.FirstName = User.FirstName;
				status.LastName = User.LastName;
				status.SIP = User.SIP;

				// check if in session
				if (null != operatorSession)
				{
					// get agent specic status
					status.InSession = true;
					status.InSessionWrap = operatorSession.CustomerSession.Status == EFS.RC.CC.Sessions.OfficeService.Customers.Customer.SessionStatus.Terminated;
					status.CustomerInfo = operatorSession.CustomerSession.DisplayName;

					// get conversation info
					status.ConversationInfo = operatorSession.CustomerSession.OfficeServiceConference.GetConversationInfoMessage();
				}
				else
				{
					status.CustomerInfo = "";
					status.InSession = false;
					status.InSessionWrap = false;
					status.ConversationInfo = null;
				}

				// distribution
				if (agentDistributionList.Count > 0)
				{
					// add ACD
					System.Collections.Specialized.StringCollection acdList = new System.Collections.Specialized.StringCollection();
					foreach (EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution in agentDistributionList)
					{
						acdList.Add(agentDistribution.AgentsDistribution.CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.URI);
					}

					// copy
					status.ACD = new System.String[acdList.Count];
					acdList.CopyTo(status.ACD, 0);
				}
				else
				{
					status.ACD = null;
				}

				// set skills
				if (null == status.Skills && skillsDictionary.Count > 0)
				{
					// init skills
					status.Skills = new EFS.RC.CC.Messages.Agents.Skills.Skill[skillsDictionary.Count];
					
					// copy all
					System.Int32 index = 0;
					foreach(EFS.RC.CC.Deployment.Entities.Operators.Skills.Skill skill in skillsDictionary.Values)
					{
						status.Skills[index++] = new EFS.RC.CC.Messages.Agents.Skills.Skill() { Id = skill.SkillID.Value, Value = skill.SkillValue.Value };
					}
				}
				else
				{
					status.Skills = new EFS.RC.CC.Messages.Agents.Skills.Skill[0];
				}
			}

			// done
			return status;
		}

		/// <summary>
		/// Get skill level by skill ID, returns 0 if skill not available
		/// </summary>
		/// <param name="skillId"></param>
		/// <returns></returns>
		public System.Int32 GetSkillLevel(System.Int32 skillId)
		{
			lock (syncAccess)
			{
				// find skill
				if (skillsDictionary.ContainsKey(skillId))
				{
					return skillsDictionary[skillId].SkillValue.Value;
				}

				// no skill
				return 0;
			}
		}

		/// <summary>
		/// Deployment reload
		/// </summary>
		/// <returns></returns>
		public override void ReloadDeployment()
		{
			// call base
			base.ReloadDeployment();

			// load
			LoadDeployment();
		}

		/// <summary>
		/// Remove agent
		/// </summary>
		public void DecomissionAgent()
		{
			// lock
			lock (syncAccess)
			{
				// remove all distributions
				foreach (EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution in agentDistributionList.ToArray())
				{
					// stop register 
					agentDistribution.EndDistribute();
				}
			}
		}

		#endregion

		#region Runtime

		/// <summary>
		/// Changed availability
		/// </summary>
		/// <param name="oldAvailability"></param>
		/// <param name="newAvailability"></param>
		private void AgentAvailabilityChanged(System.Boolean oldAvailability, System.Boolean newAvailability)
		{
			// set to hunt group
			if (newAvailability)
			{
				agentsHunterQueue.AgentAvailable(this);
			}
			else
			{
				agentsHunterQueue.AgentUnAvailable(this);
			}
		}

		/// <summary>
		/// Added to distribution
		/// </summary>
		/// <param name="agentDeploymentId"></param>
		private void AddedToDistribution(EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution)
		{
			// check
			EFS.Common.Verify.Reference(agentDistribution, "agentDistribution");

			// check 
			if (!agentDistribution.AgentPresence.Equals(this))
			{
				// wrong
				throw new System.InvalidOperationException("Added to different Distribution");
			}

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (!agentDistributionList.Contains(agentDistribution))
				{
					// add
					agentDistributionList.Add(agentDistribution);
				}
			}

			// notify that is now available
			agentsHunterQueue.StartMatch();
		}

		/// <summary>
		/// Removed from distribution
		/// </summary>
		/// <param name="agentDistribution"></param>
		private void RemovedFromDistribution(EFS.RC.CC.Presences.Operators.Distribution.Agent agentDistribution)
		{
			// check
			EFS.Common.Verify.Reference(agentDistribution, "agentDistribution");

			// check 
			if (!agentDistribution.AgentPresence.Equals(this))
			{
				// wrong
				throw new System.InvalidOperationException("Removed from different Distribution");
			}

			// loack access
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check
				if (agentDistributionList.Contains(agentDistribution))
				{
					// un-register events
					agentDistribution.OnDistributeStopped -= RemovedFromDistribution;
					agentDistribution.OnDistributeReady -= AddedToDistribution;

					// add
					agentDistributionList.Remove(agentDistribution);
				}
			}
		}

		/// <summary>
		/// Load deployment
		/// </summary>
		private void LoadDeployment()
		{
			// lock
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// remove skills
				skillsDictionary.Clear();

				// get distribution
				EFS.RC.CC.Deployment.Entities.Operators.Skills.Skill[] skillsArray = EFS.RC.CC.Deployment.Tables.Tools.AgentSkills.GetByAgentID(OperatorPresenceDeployment.ID.Value);

				// check
				EFS.Common.Verify.Reference(skillsArray, "skillsArray");

				// add
				foreach (EFS.RC.CC.Deployment.Entities.Operators.Skills.Skill skill in skillsArray)
				{
					skillsDictionary[skill.SkillID.Value] = skill;
				}
			}

			// deployment changes
			if (null != OnDeploymentLoad)
			{
				try
				{
					OnDeploymentLoad(this);
				}
				catch (System.Exception ex)
				{
					// log
					EFS.EventLog.Log.LogException(ex, this);
				}
			}
		}

		/// <summary>
		/// Agent session terminated
		/// </summary>
		private void OnOperatorSessionTerminated(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnSessionTerminated", this);

			// check
			EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

			// cast
			EFS.RC.CC.Sessions.OfficeService.Operators.Operator operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.Operator;

			// check
			EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// sync
			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check if same session or some orphan
				if (this.operatorSession.Equals(operatorSession))
				{
					this.operatorSession = null;
				}
			}
		}

		#endregion
	}
}
